///////////////////////////////////////////////////////////////////////////////
//
//  This file is part of MathLib.NET.
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//  
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//  
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library;  If not, see 
//  <http://www.gnu.org/licenses/>.
//
///////////////////////////////////////////////////////////////////////////////

using ILNumerics;
using ILNumerics.BuiltInFunctions;

// Elementary Matrices and Arrays functions
// ========================================
//
// blkdiag - Construct block diagonal matrix from input arguments 
// diag - Diagonal matrices and diagonals of matrix 
// eye - Identity matrix 
// freqspace - Frequency spacing for frequency response 
// ind2sub - Subscripts from linear index 
// linspace - Generate linearly spaced vectors 
// logspace - Generate logarithmically spaced vectors 
// meshgrid - Generate X and Y arrays for 3-D plots 
// ndgrid - Generate arrays for N-D functions and interpolation 
// ones - Create array of all ones 
// rand - Uniformly distributed pseudorandom numbers 
// randi - Uniformly distributed pseudorandom integers 
// randn - Normally distributed pseudorandom numbers 
// RandStream - Random number stream 
// sub2ind - Convert subscripts to linear indices 
// zeros - Create array of all zeros 

namespace MathLib
{
    /// <summary>
    /// Elementary Matrices and Arrays functions.
    /// </summary>
    public partial class MLMath
    {
        /// <summary>
        /// Generate identity matrix.
        /// </summary>
        /// <param name="n">Size of leading dimension</param>
        /// <param name="m">Size of other dimension</param>
        /// <returns>Identify matrix of specified size</returns>
        public static ILArray<double> eye(int n, int m = -1)
        {
            if (m == -1) m = n;
            return ILMath.eye(n, m);
        }

        /// <summary>
        /// Generate matrix filled with 1s.
        /// </summary>
        /// <param name="args">Arguments</param>
        /// <returns>Matrix filled with ones</returns>
        public static ILBaseArray ones(params object[] args)
        {
            if (args[args.Length - 1] is string)
            {
                ILBaseArray outArray;
                if (args[0] is IronPython.Runtime.List)
                {
                    IronPython.Runtime.List list = args[0] as IronPython.Runtime.List;
                    int[] dims = new int[list.Count];
                    for (int i = 0; i < dims.Length; i++) dims[i] = (int)list[i];
                    outArray = ILMath.ones(dims);
                }
                else
                {
                    if (args.Length == 2)
                    {
                        int[] dims = new int[] { (int)args[0], (int)args[0] };
                        return ILMath.ones(dims);
                    }
                    else
                    {
                        int[] dims = new int[args.Length - 1];
                        for (int i = 0; i < args.Length - 1; i++) dims[i] = (int)args[i];
                        return ILMath.ones(dims);
                    }
                }

                string outType = (string)args[args.Length - 1];
                if (outType == "double")
                    return outArray;
                else if (outType == "single")
                    return ILMath.tosingle(outArray);
                else if (outType == "int8")
                    return ILMath.tochar(outArray);
                else if (outType == "uint8")
                    return ILMath.tobyte(outArray);
                else if (outType == "int16")
                    return ILMath.toint16(outArray);
                else if (outType == "uint16")
                    return ILMath.touint16(outArray);
                else if (outType == "int32")
                    return ILMath.toint32(outArray);
                else if (outType == "uint32")
                    return ILMath.touint32(outArray);
                else if (outType == "int64")
                    return ILMath.toint64(outArray);
                else if (outType == "uint64")
                    return ILMath.touint64(outArray);
                else return null;
            }
            else
            {
                if (args[0] is IronPython.Runtime.List)
                {
                    IronPython.Runtime.List list = args[0] as IronPython.Runtime.List;
                    int[] dims = new int[list.Count];
                    for (int i = 0; i < dims.Length; i++) dims[i] = (int)list[i];
                    return ILMath.ones(dims);
                }
                else
                {
                    if (args.Length == 1)
                    {
                        int[] dims = new int[] { (int)args[0], (int)args[0] };
                        return ILMath.ones(dims);
                    }
                    else
                    {
                        int[] dims = new int[args.Length];
                        for (int i = 0; i < args.Length; i++) dims[i] = (int)args[i];
                        return ILMath.ones(dims);
                    }
                }
            }
        }

        /// <summary>
        /// Generate matrix filled with 0s.
        /// </summary>
        /// <param name="args">Arguments</param>
        /// <returns>Matrix filled with zeros</returns>
        public static ILBaseArray zeros(params object[] args)
        {
            // Extract from python tuple, if necessary
            if (args[0] is IronPython.Runtime.PythonTuple)
            {
                IronPython.Runtime.PythonTuple pt = args[0] as IronPython.Runtime.PythonTuple;
                args = new object[pt.Count];
                for (int i = 0; i < pt.Count; i++)
                    args[i] = pt[i];
            }

            if (args[args.Length - 1] is string)
            {
                ILBaseArray outArray;
                if (args[0] is IronPython.Runtime.List)
                {
                    IronPython.Runtime.List list = args[0] as IronPython.Runtime.List;
                    int[] dims = new int[list.Count];
                    for (int i = 0; i < dims.Length; i++) dims[i] = (int)double.Parse(list[i].ToString());
                    outArray = ILMath.zeros(dims);
                }
                else
                {
                    int[] dims = new int[args.Length - 1];
                    for (int i = 0; i < args.Length - 1; i++) dims[i] = (int)double.Parse(args[i].ToString());
                    outArray = ILMath.zeros(dims);
                }

                string outType = (string)args[args.Length - 1];
                if (outType == "double")
                    return outArray;
                else if (outType == "single")
                    return ILMath.tosingle(outArray);
                else if (outType == "complex")
                    return ILMath.tocomplex(outArray);
                else if (outType == "fcomplex")
                    return ILMath.tofcomplex(outArray);
                else if (outType == "int8")
                    return ILMath.tochar(outArray);
                else if (outType == "uint8")
                    return ILMath.tobyte(outArray);
                else if (outType == "int16")
                    return ILMath.toint16(outArray);
                else if (outType == "uint16")
                    return ILMath.touint16(outArray);
                else if (outType == "int32")
                    return ILMath.toint32(outArray);
                else if (outType == "uint32")
                    return ILMath.touint32(outArray);
                else if (outType == "int64")
                    return ILMath.toint64(outArray);
                else if (outType == "uint64")
                    return ILMath.touint64(outArray);
                else return null;
            }
            else
            {
                if (args[0] is IronPython.Runtime.List)
                {
                    IronPython.Runtime.List list = args[0] as IronPython.Runtime.List;
                    int[] dims = new int[list.Count];
                    for (int i = 0; i < dims.Length; i++) dims[i] = (int)double.Parse(list[i].ToString());
                    return ILMath.zeros(dims);
                }
                else
                {
                    int[] dims = new int[args.Length];
                    for (int i = 0; i < args.Length; i++) dims[i] = (int)double.Parse(args[i].ToString());
                    return ILMath.zeros(dims);
                }
            }
        }

        /// <summary>
        /// Convert linear indices to subscripts
        /// Wrapping ILNumerics ind2sub. Therefore is not optimal.
        /// </summary>
        /// <param name="siz">Vector with dimensions to calculate subscripts for</param>
        /// <param name="IND">linear indices</param>
        /// <returns>vector of matrices with subscripts</returns>
        public static ILArray<int>[] ind2sub(int[] siz, ILArray<int> IND)
        {
            ILArray<int>[] subscripts = new ILArray<int>[siz.Length];
            ILArray<int> A = new ILArray<int>(siz); //for ILNumerics ind2sub

            for (int i = 0; i < subscripts.Length; ++i)
            {
                subscripts[i] = new ILArray<int>(IND.Dimensions.ToIntArray());
            }

            for (int i = 0; i < IND.InternalArray4Experts.Length; ++i) 
            {
                int[] ss = ILMath.ind2sub(A, IND.InternalArray4Experts[i]);
                for (int j = 0; j < subscripts.Length; ++j)
                {
                    subscripts[j].InternalArray4Experts[i] = ss[j];
                }
            }

            return subscripts;
        }

        /// <summary>
        /// Convert subscripts to linear indices
        /// No wrapping
        /// </summary>
        /// <param name="arraySize">vector with dimensions of subscripts</param>
        /// <param name="Sub">vector of matrices with subscripts. matrices must be with the same sizes</param>
        /// <returns>linear indices</returns>
        public static ILArray<int> sub2ind(int[] arraySize, ILArray<int>[] Sub)
        {
            ILArray<int> linears = new ILArray<int>(Sub[0].Dimensions.ToIntArray());
            ILArray<int> A = new ILArray<int>(arraySize); //for ILNumerics sub2ind

            for (int i = 0; i < linears.InternalArray4Experts.Length; ++i)
            {
                linears.InternalArray4Experts[i] = 0;
                int mul = 1;
                for (int j = 0; j < Sub.Length; ++j)
                {
                    linears.InternalArray4Experts[i] += Sub[j].InternalArray4Experts[i] * mul;
                    mul *= arraySize[j];
                }
            }

            return linears;
        }
    }
}
